﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;

namespace Play
{
    public class NimGame
    {
        private NimState TreeRoot;
        public int LeafsCount;
        private MinimaxType _minimaxType;

        public NimGame(int[] heaps)
        {
            TreeRoot = new NimState(heaps, null, PlayerType.Max, null);
            BuildTree(TreeRoot);
        }

        private void BuildTree(NimState state)
        {
            if (!state.HasPieces())
            {
                return;
            }

            for (var i = 0; i < state.Heaps.Length; i++)
            {
                var heap = state.Heaps[i];
                for (var j = 1; j <= heap; j++)
                {
                    var heapId = i;
                    var pieces = j;

                    var move = new NimMove(heapId, pieces);
                    var childState = ApplyMove(state, move);
                    state.Children.Add(childState);
                    BuildTree(childState);
                }
            }
        }

        private NimState ApplyMove(NimState state, NimMove move)
        {
            //remove move.p from state.p
            var heaps = (int[])state.Heaps.Clone();
            PlayerType playerType = (state.PlayerType == PlayerType.Max) ? PlayerType.Min : PlayerType.Max;

            var newState = new NimState(heaps, state, playerType, move);
            newState.RemovePieces(move);

            return newState;
        }

        public void CalcMiniMax(MinimaxType minimaxType)
        {
            _minimaxType = minimaxType;
            switch (minimaxType)
            {
                case MinimaxType.Full:
                    CalcMiniMaxFull(TreeRoot);
                    break;
                case MinimaxType.Prune:
                    CalcMiniMaxPrune(TreeRoot);
                    break;
            }
        }

        private void CalcMiniMaxFull(NimState state)
        {
            //this is a leaf: recursion stops
            if (!state.Children.Any())
            {
                state.MiniMax = (state.PlayerType == PlayerType.Max) ? 1 : -1;
                LeafsCount++;

                return;
            }

            foreach (var child in state.Children)
            {
                CalcMiniMaxFull(child);
                state.UpdateMinimax(child.MiniMax);
            }
        }

        private void CalcMiniMaxPrune(NimState state)
        {
            //this is a leaf: recursion stops
            if (!state.Children.Any())
            {
                state.MiniMax = (state.PlayerType == PlayerType.Max) ? 1 : -1;
                LeafsCount++;

                state.UpdateAlphaBeta(state.MiniMax);
                return;
            }

            foreach (var child in state.Children)
            {
                child.Alpha = state.Alpha;
                child.Beta = state.Beta;

                CalcMiniMaxPrune(child);

                state.UpdateMinimax(child.MiniMax);
                state.UpdateAlphaBeta(child.MiniMax);

                //stop examine other childs if prune needed
                if (state.IsPruneNeeded())
                {
                    state.Pruned = true; //prune on this state
                    break;
                }
            }
        }

        public void PrintRecommended()
        {
            var heapStr = string.Join(",",TreeRoot.Heaps);

            Console.WriteLine("=================================================");
            Console.WriteLine("Initial configuration: " + heapStr);
            Console.WriteLine("Minimax type: " + _minimaxType);
            Console.WriteLine("# of Leafs: " + LeafsCount);
            Console.WriteLine("Root minimax is: " + TreeRoot.MiniMax);
            Console.WriteLine("");
            Console.WriteLine("Recommended move/s are:");

            PrintRecommendedMoves(TreeRoot);

            //PrintRecommendedPath(TreeRoot);
        }

        private void PrintRecommendedMoves(NimState state)
        {
            var examinedChildren = state.Children.Where(c => !c.Pruned && c.MiniMax != 0).ToList();
            if (!examinedChildren.Any())
            {
                Console.WriteLine("There is no move to output");
                return;
            }

            int bestMinMax = state.PlayerType == PlayerType.Max
                ? examinedChildren.Max(c => c.MiniMax)
                : examinedChildren.Min(c => c.MiniMax);

            foreach (var child in examinedChildren.Where(c => c.MiniMax == bestMinMax))
            {
                Console.WriteLine(child.Move);
            }

        }

        //private void PrintRecommendedPath(NimState state)
        //{
        //    if (!state.Children.Any())
        //    {
        //        //this is a leaf print states path:
        //        PrintPath(state);
        //        Console.WriteLine("=== GAME OVER: " + state.PlayerType + " Wins! ===");
        //        //Console.WriteLine("");
        //        return;
        //    }

        //    //all childs have been pruned
        //    var examinedChildren = state.Children.Where(c => !c.Pruned && c.MiniMax != 0).ToList();
        //    if (!examinedChildren.Any())
        //        return;

        //    int bestMinMax = state.PlayerType == PlayerType.Max
        //        ? examinedChildren.Max(c => c.MiniMax)
        //        : examinedChildren.Min(c => c.MiniMax);

        //    var child = examinedChildren.First(c => c.MiniMax == bestMinMax);
        //    PrintRecommendedPath(child);
        //}

        //private static void PrintPath(NimState leafState)
        //{
        //    var stack = new Stack<NimState>();
        //    var state = leafState;
        //    while (state != null)
        //    {
        //        stack.Push(state);
        //        state = state.Parent;
        //    }

        //    while (stack.Any())
        //    {
        //        Console.WriteLine(stack.Pop().ToString());
        //    }
        //}
    }
}